/*
 WordFinder.m
 
 Created by cool8jay on 2/16/10.
 Copyright 2010 cool8jay@qq.com All rights reserved.
 */
#import "WordFinder.h"

#import "ISpeLLController.h"

@implementation WordFinder

- (id)initWithLetters:(NSString*)inputLetters controller:(ISpeLLController*)windowController
{
	self = [super init];
	if (self!=nil) {
			// Remember the controller; this is used to send the controller the found words.
			// Note: WordFinder could use NSNotifications to announce the words that it
			//		 finds instead, rather than keeping a reference to ISpeLLController.
			//		 That would require a more code, but would completely decouple
			//		 the two classes.
		controller = windowController;
		
			// Convert the string into an array of character value objects.
			// Only lower case letters are added to the set; all other characters are ignored
		
		inputLetters=[inputLetters lowercaseString];
		NSCharacterSet *lowercaseLetters = [NSCharacterSet lowercaseLetterCharacterSet];
		NSMutableArray *set = [NSMutableArray arrayWithCapacity:[inputLetters length]];
		unsigned int i;
		int inputLettersLength=[inputLetters length];
		for (i=0; i<inputLettersLength; i++)
		{
			unichar c = [inputLetters characterAtIndex:i];
			if ([lowercaseLetters characterIsMember:c])
				[set addObject:[NSNumber numberWithUnsignedShort:c]];
		}
			// Keep an immutable copy of the character value array
		letterSet = [NSArray arrayWithArray:set];
	}
	return self;
}

/*
 Search for all of the words that can be spelled using the input letter set.
 This is the operation's main method; invoked by NSOperationQueue to execute the operation.
 */
- (void)main
{	
		// Get the list of possible words
	NSArray* dictionary = controller.dictionaryWords;
	
	/*
	 First, signal to the controller that a new word searching will be started and ask the controller 
	 to some preparation work: clean the table.
	 
	 The clearance work is to make room for new search. 
	 
	 If for now this is not the first time of searching, it is indispensable, otherwise the result from the 
	 former searching will stay in the new searhing result, which is definitely wrong.
	 
	 But for the first time of searching, we still do the clearance work, like we are cleaning an empty thing.
	 */
	[controller performSelectorOnMainThread:@selector(removeWords) withObject:nil waitUntilDone:YES];
	
	/* 
	 The dictionary array contains only multi-character words, longer than 2.
	 If the letter set is too small to find anything, terminate the search early.
	 */
	int inputLetterSetLength=[letterSet count];
	if (inputLetterSetLength<2)
	{
		[controller.spellingProgressIndicator stopAnimation:nil];
		return;
	}
	
		// Brute force search of every word in the dictionary...
	for ( NSString *candidate in dictionary ) {
		Boolean flag=TRUE; 
		int l = [candidate length];
		
			// optimization: no point in examining words that are longer that the set of letters
		if (l>inputLetterSetLength)
			continue;
		
			// Here uses the judgement of subset arithmetic.
			// First, make a working copy of input letter set
		NSMutableArray *testSet = [NSMutableArray arrayWithArray:letterSet];	
		while (l--) {
				// Test each character by trying to delete it from the working set
			unichar c = [candidate characterAtIndex:l];
			unsigned int i = [testSet indexOfObject:[NSNumber numberWithUnsignedShort:c]];
			
				// Candidate word contains a letter that isn't in the input letter set, so it can not 
				// be spelled out by your input letters, we should jump out of the judgement.
			if (i==NSNotFound)
			{
					// This candidate word should not be added into the result list
				flag=FALSE;
				break;
			}					
				// Remove used letter in input letter set, if without this step, situation like this 
				// will happen:	you input letter set is "two", but the result contains "too", because
				// the "o" in "two" is used twice to check the 2 "o's" in "too".
			[testSet removeObjectAtIndex:i];
		}
			// Till this point, the candidate word pass all letter check, so it can be spelled using
			// the input set. We tell the controller (on the main thread) to add it to the result list.
		if(flag){
			[controller performSelectorOnMainThread:@selector(foundWord:) withObject:candidate waitUntilDone:YES];			
		}
		
			// Cancellation of NSOperation checks.
		if ([self isCancelled]){break;}
	}
	[controller.spellingProgressIndicator stopAnimation:nil];
}

-(void) filterWordsInRange:(int)minLength{
	NSArray* dictionary = controller.dictionaryWords;
	
	int inputLetterSetLength=[letterSet count];
	if (inputLetterSetLength<2)
	{
		[controller.spellingProgressIndicator stopAnimation:nil];
		return;
	}
	
		// Brute force search of every word in the dictionary...
	for ( NSString *candidate in dictionary ) {
		Boolean flag=TRUE; 
		int l = [candidate length];
		
			// optimization: no point in examining words that are longer that the set of letters
		if (l>inputLetterSetLength)
			continue;
		
			// Here uses the judgement of subset arithmetic.
			// First, make a working copy of input letter set
		NSMutableArray *testSet = [NSMutableArray arrayWithArray:letterSet];	
		while (l--) {
				// Test each character by trying to delete it from the working set
			unichar c = [candidate characterAtIndex:l];
			unsigned int i = [testSet indexOfObject:[NSNumber numberWithUnsignedShort:c]];
			
				// Candidate word contains a letter that isn't in the input letter set, so it can not 
				// be spelled out by your input letters, we should jump out of the judgement.
			if (i==NSNotFound)
			{
					// This candidate word should not be added into the result list
				flag=FALSE;
				break;
			}					
				// Remove used letter in input letter set, if without this step, situation like this 
				// will happen:	you input letter set is "two", but the result contains "too", because
				// the "o" in "two" is used twice to check the 2 "o's" in "too".
			[testSet removeObjectAtIndex:i];
		}
			// Till this point, the candidate word pass all letter check, so it can be spelled using
			// the input set. We tell the controller (on the main thread) to add it to the result list.
		if(flag){
			[controller performSelectorOnMainThread:@selector(foundWord:) withObject:candidate waitUntilDone:YES];			
		}
		
			// Cancellation of NSOperation checks.
		if ([self isCancelled]){break;}
	}
}

@end
